class_name AudioSystem
extends BaseSystem

## 音频系统，管理全局音频

@export var bgm_vol_db:float
@export var bgm_audio_bus_idx:int = 0
@export var sfx_vol_db:float
@export var sfx_audio_bus_idx:int = 1
@export var stream_player_bgm_init_count:int = 1
@export var stream_player_init_count:int = 5
@export var stream_player_2d_init_count:int = 5
@export var sfxs:Array[SFXInfo]
var stream_players_bgm:Array[AudioStreamPlayer] = []
var stream_players:Array[AudioStreamPlayer] = []
var stream_player_2ds:Array[AudioStreamPlayer2D] = []
var cur_stream_player_bgm_idx:int = 0
var cur_stream_player_idx:int = 0
var cur_stream_player_2d_idx:int = 0
var next_play_id:int = 0
var play_dict:Dictionary = {}
#region 属性
#endregion
#region 字段
#endregion
#region 信号
#endregion

#region 基类方法
func init_system(args = null):
	super(args)
	set_bgm_vol(bgm_vol_db)
	set_sfx_vol(sfx_vol_db)
	GameMessageSystem.observe(self,"Audio","",_on_audio_message_received)
#endregion
#region 公共方法
func set_bgm_vol(vol_db:float):
	AudioServer.set_bus_volume_db(bgm_audio_bus_idx,vol_db - 70)
	bgm_vol_db = vol_db
func set_sfx_vol(vol_db:float):
	AudioServer.set_bus_volume_db(sfx_audio_bus_idx,vol_db - 30)
	sfx_vol_db = vol_db
func set_bgm_mute(enable:bool):
	AudioServer.set_bus_mute(bgm_audio_bus_idx,enable)
func set_sfx_mute(enable:bool):
	AudioServer.set_bus_mute(sfx_audio_bus_idx,enable)
## 播放BGM
func play_bgm(sfx_id:String) -> int:
	var sfx_info:SFXInfo = _get_sfx_info_by_sfx_id(sfx_id)
	if not sfx_info:
		return -1
	var stream_idx = _get_not_playing_stream_bgm_idx()
	var stream:AudioStreamPlayer = stream_players_bgm[stream_idx]
	if stream.playing:
		stream.stop()
	stream.stream = sfx_info.sfx
	cur_stream_player_bgm_idx = stream_idx + 1
	cur_stream_player_bgm_idx %= stream_player_bgm_init_count
	stream.play()
	var play_id = next_play_id
	next_play_id += 1
	var last_play_id = stream.get_meta("play_id")
	if last_play_id and play_dict.has(last_play_id):
		play_dict.erase(last_play_id)
	stream.set_meta("play_id",play_id)
	play_dict[play_id] = stream
	return play_id
## 播放非空间声音
func play_sfx(sfx_id:String) -> int:
	var sfx_info:SFXInfo = _get_sfx_info_by_sfx_id(sfx_id)
	if not sfx_info:
		return -1
	var stream_idx = _get_not_playing_stream_idx()
	var stream:AudioStreamPlayer = stream_players[stream_idx]
	if stream.playing:
		stream.stop()
	stream.stream = sfx_info.sfx
	cur_stream_player_idx = stream_idx + 1
	cur_stream_player_idx %= stream_player_init_count
	stream.play()
	var play_id = next_play_id
	next_play_id += 1
	var last_play_id = stream.get_meta("play_id")
	if last_play_id and play_dict.has(last_play_id):
		play_dict.erase(last_play_id)
	stream.set_meta("play_id",play_id)
	play_dict[play_id] = stream
	return play_id
## 播放空间声音
func play_sfx_2d(sfx_id:String,pos:Vector2) -> int:
	var sfx_info:SFXInfo = _get_sfx_info_by_sfx_id(sfx_id)
	if not sfx_info:
		return -1
	var stream_idx = _get_not_playing_stream_2d_idx()
	var stream = stream_player_2ds[stream_idx]
	if stream.playing:
		stream.stop()
	stream.stream = sfx_info.sfx
	stream.global_position = pos
	cur_stream_player_2d_idx = stream_idx + 1
	cur_stream_player_2d_idx %= stream_player_2d_init_count
	stream.play()
	var play_id = next_play_id
	next_play_id += 1
	var last_play_id = stream.get_meta("play_id")
	if last_play_id and play_dict.has(last_play_id):
		play_dict.erase(last_play_id)
	stream.set_meta("play_id",play_id)
	play_dict[play_id] = stream
	return play_id
## 停止播放声音
func stop_sfx(play_id):
	if not play_dict.has(play_id):
		return
	var stream = play_dict.get(play_id)
	if stream and stream.get_meta("play_id") == play_id:
		stream.stop()
## 停止放所有声音
func stop_all_sfx():
	for play_id:int in play_dict.keys():
		stop_sfx(play_id)
func destroy_all_sfx_player():
	for player in stream_players_bgm:
		if player.playing:
			player.stop()
		player.queue_free()
	for player in stream_players:
		if player.playing:
			player.stop()
		player.queue_free()
	for player in stream_player_2ds:
		if player.playing:
			player.stop()
		player.queue_free()
#endregion
#region 私有方法
func _get_sfx_info_by_sfx_id(sfx_id:String):
	var res = sfxs.filter(func (x:SFXInfo):return x.sfx_id == sfx_id)
	if res.size() > 0:
		return res[0]
	return null
func _get_not_playing_stream_bgm_idx() -> int:
	var idx = cur_stream_player_bgm_idx
	for i in range(stream_player_bgm_init_count):
		var stream = stream_players_bgm[idx]
		if not stream.playing:
			return idx
		idx += 1
		idx %= stream_player_bgm_init_count
	return cur_stream_player_bgm_idx
func _get_not_playing_stream_idx() -> int:
	var idx = cur_stream_player_idx
	for i in range(stream_player_init_count):
		var stream = stream_players[idx]
		if not stream.playing:
			return idx
		idx += 1
		idx %= stream_player_init_count
	return cur_stream_player_idx
func _get_not_playing_stream_2d_idx() -> int:
	var idx = cur_stream_player_2d_idx
	for i in range(stream_player_2d_init_count):
		var stream = stream_player_2ds[idx]
		if not stream.playing:
			return idx
		idx += 1
		idx %= stream_player_2d_init_count
	return cur_stream_player_2d_idx
func _on_audio_message_received(m_namespace:String,m_tag:String,args:Array):
	if m_tag == "Play_BGM":
		play_bgm(args[0])
	elif m_tag == "Play_SFX":
		play_sfx(args[0])
	elif m_tag == "Play_SFX_2D":
		play_sfx_2d(args[0],args[1])
#endregion
#region 生命周期函数
func _init() -> void:
	for i in range(stream_player_bgm_init_count):
		var stream = AudioStreamPlayer.new()
		add_child(stream)
		stream.autoplay = false
		stream.bus = AudioServer.get_bus_name(bgm_audio_bus_idx)
		stream_players_bgm.append(stream)
	for i in range(stream_player_init_count):
		var stream = AudioStreamPlayer.new()
		add_child(stream)
		stream.autoplay = false
		stream.bus = AudioServer.get_bus_name(sfx_audio_bus_idx)
		stream_players.append(stream)
	for i in range(stream_player_2d_init_count):
		var stream = AudioStreamPlayer2D.new()
		add_child(stream)
		stream.autoplay = false
		stream.bus = AudioServer.get_bus_name(sfx_audio_bus_idx)
		stream_player_2ds.append(stream)
func _notification(what: int) -> void:
	if what == NOTIFICATION_WM_CLOSE_REQUEST:
		GameMessageSystem.clear_namespace("Audio")
		destroy_all_sfx_player()
#endregion
